<?php
/*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 3
* of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
*/


/**
 * @name DidYouMean
 * @copyright 2009  MP2P Technologies S.A.
 * 
 */

if ( ! defined( 'MEDIAWIKI' ) )
die();

require_once("DatabaseFunctions.php");

#install extension hook
$wgHooks['BeforePageDisplay'][] = 'DidYouMean';

function DidYouMean ( &$out, &$sk )
{
	global $wgTitle, $wgRequest;
	$action = $wgRequest->getText('action', '');
	if ($action!='') return true;

	switch ($wgTitle->mNamespace)
	{
		case NS_ARTIST:
			execute_didyoumean($out, 'artist', $wgTitle->mArtist);
			break;
		case NS_SONG:
			execute_didyoumean($out, 'song', $wgTitle->mArtist, $wgTitle->mNoArtist);
			break;
		case NS_RELEASE:
			execute_didyoumean($out, 'release', $wgTitle->mArtist, $wgTitle->mNoArtist);
			break;
		case NS_SPECIAL:
			$search = $wgRequest->getText('search', '');
			if ($search!='') execute_didyoumean($out, 'search', $search);
			break;
	}
	return true;
}

function execute_didyoumean(&$out, $mode, $term1, $term2 = null){
    global $wgDBprefix, $wgScriptPath, $wgTitle;
    
    wfProfileIn("ss_didyoumean");

    $searching = false;	
    $term1Sql = mb_ereg_replace(' ', '* ', mysql_real_escape_string(sphinx_escape($term1))).'*';
    $term1Key = normalize($term1);
    $term1KeyLike = '%'.implode('%', mb_string2array(mb_substr($term1Key,0,4))).'%'.mb_substr($term1Key,4).'%';
    if ($term2==null) {
      $urlArray = array($term1);
    } else {
      $term2Sql = mb_ereg_replace(' ', '* ', mysql_real_escape_string(sphinx_escape($term2))).'*';
      $term2Key = normalize($term2);
      $term2KeyLike = '%'.implode('%', mb_string2array(mb_substr($term2Key,0,4))).'%'.mb_substr($term2Key,4).'%';
      $urlArray = array($term1, $term2);
    }

    switch($mode)
    {
              case 'search':
                      $sql = "select max(titleWeight) titleWeight, sum(titleWeight) albumWeight, coalesce((select sum(g.counttags) from ss_genres g where g.artistkey = titles.artistkey),0) artistWeight, albumKey, common_string(10, 100, titlekey, titleWeight) titleKey, common_string(10, 100, artistkey, titleWeight) artistkey, common_string(10, 150, artist, titleWeight) artist, common_string(10, 100, album, titleWeight) album, common_string(10, 100, title, titleWeight) title from (select coalesce(s.int1,0) as titleWeight, artistkey, titleKey, albumKey, artist, album, title from searches s inner join ss_tags t on s.idtags=t.idtags where query='$term1Sql;mode=any;groupby=attr:titleKey;select=idtags as idtags,sum(@weight+countFiles) as int1;groupsort=@weight desc, int1 desc;sort=extended:@weight desc, countTagsOk desc;limit=100;maxmatches=10000;maxquerytime=750;filter=type,1') titles group by artistkey, albumkey order by artistWeight desc";
		      $maxSimil = min(ceil(mb_strlen($term1Key)*0.4), 2);
                      $urlTemplate = URL_SEARCH;
		      $searching = true;
                      break;

              case 'release':
                      $sql = "select Artist term1, ArtistKey term1Key, '' term2, l, Count, w, weight, 1+log(Count+1)*weight*log(w+1)/(l+1) w2 from (select t.artistKey, common_string(10,150,g.Artist) Artist, s.Count, sum(g.counttags*(g.filestagsok+1)) as w, s.weight, if(t.artistKey LIKE '$term1KeyLike', 1, levenshtein(t.artistKey, '$term1Key')) l from ss_tags t inner join ss_genres g on g.artistKey=t.artistKey inner join searches s on s.idtags=t.idtags where query='$term1Sql;mode=any;groupby=attr:artistKey;select=idtags as idtags,sum(TotalCount) as Count;sort=extended:@weight desc;groupsort=@weight desc, Count desc;limit=30;maxmatches=10000;maxquerytime=250;filter=type,1' group by g.artistkey) similar where l<4 order by w2 desc;";
		      $maxSimil = min(ceil(mb_strlen($term1Key)*0.4), 3);
		      $similars = getSimilarTags($sql, false, $term1, $term1Key, null, null, $maxSimil, 1);
		      $where = "g.artistKey='$term1Key'";
		      foreach ($similars as $pair)
			$where .= " OR g.artistKey='".normalize($pair[0])."'";
		      
		      $sql = "select Artist term1, ArtistKey term1Key, Album term2, AlbumKey term2Key, queryCount, tagWeight, queryWeight, log(queryCount+1)*queryWeight*log(tagWeight+1)/(simil+1) finalWeight from (select common_string(10, 150, g.artistKey) as ArtistKey, common_string(10,150,g.Artist) Artist, t.albumKey, common_string(10,100,g.album) Album, s.Count queryCount, sum(g.countfiles*(g.counttagsok+1)) as tagWeight, s.weight queryWeight, if(t.albumKey LIKE '$term2KeyLike', 1, levenshtein(t.albumKey, '$term2Key')) simil from ss_tags t inner join ss_tags g on g.albumKey=t.albumKey inner join searches s on s.idtags=t.idtags where ($where) and query='$term2Sql;mode=any;groupby=attr:albumKey;select=idtags as idtags,sum(TotalCount) as Count;sort=extended:@weight desc;groupsort=@weight desc, Count desc;limit=30;maxmatches=10000;maxquerytime=250;filter=type,1' group by g.albumKey) similar where simil<4 order by finalWeight desc;";
		      $maxSimil = min(ceil(mb_strlen($term2Key)*0.4), 3);
                      $urlTemplate = URL_RELEASE;
		      break;

              case 'artist':
                      $sql = "select Artist term1, ArtistKey term1Key, NULL term2, NULL term2Key, queryCount, tagWeight, queryWeight, log(queryCount+1)*queryWeight*log(tagWeight+1)/(simil+1) finalWeight from (select t.artistKey, common_string(10,150,g.Artist) Artist, s.Count as queryCount, sum(g.counttags*(g.filestagsok+1)) as tagWeight, s.weight queryWeight, if(t.artistKey LIKE '$term1KeyLike', 1, levenshtein(t.artistKey, '$term1Key')) simil from ss_tags t inner join ss_genres g on g.artistKey=t.artistKey inner join searches s on s.idtags=t.idtags where query='$term1Sql;mode=any;groupby=attr:artistKey;select=idtags as idtags,sum(TotalCount) as Count;sort=extended:@weight desc;groupsort=@weight desc, Count desc;limit=30;maxmatches=10000;maxquerytime=250;filter=type,1' group by g.artistkey) similar where simil<4 order by finalWeight desc;";
		      $maxSimil = min(ceil(mb_strlen($term1Key)*0.4), 3);
                      $urlTemplate = URL_ARTIST;
                      break;

              case 'song':
		      $sql = "select Artist term1, ArtistKey term1Key, '' term2, l, Count, w, weight, 1+log(Count+1)*weight*log(w+1)/(l+1) w2 from (select g.artistkey, common_string(10,150,g.Artist) Artist, s.Count, sum(g.counttags*(g.filestagsok+1)) as w, s.weight, if(t.artistKey LIKE '$term1KeyLike', 1, levenshtein(t.artistKey, '$term1Key')) l from ss_tags t inner join ss_genres g on g.artistKey=t.artistKey inner join searches s on s.idtags=t.idtags where query='$term1Sql;mode=any;groupby=attr:artistKey;select=idtags as idtags, sum(TotalCount) as Count;sort=extended:@weight desc;groupsort=@weight desc, Count desc;limit=30;maxmatches=10000;maxquerytime=250;filter=type,1' group by g.artistkey) similar where l<4 order by w2 desc;";
		      $maxSimil = min(ceil(mb_strlen($term1Key)*0.4), 3);
		      $similars = getSimilarTags($sql, false, $term1, $term1Key, null, null, $maxSimil, 1);
		      $where = "g.artistKey='$term1Key'";
		      foreach ($similars as $pair)
			$where .= " OR g.artistKey='".normalize($pair[0])."'";
		      
		      $sql = "select Artist term1, ArtistKey term1Key, Title term2, TitleKey term2Key, queryCount, tagWeight, queryWeight, log(queryCount+1)*queryWeight*log(tagWeight+1)/(simil+1) finalWeight from (select g.artistKey, common_string(10,150,g.Artist) Artist, t.titleKey, common_string(10,100,g.title) Title, s.Count queryCount, sum(g.countfiles*(g.counttagsok+1)) as tagWeight, s.weight queryWeight, if(t.titleKey LIKE '$term2KeyLike', 1, levenshtein(t.titleKey, '$term2Key')) simil from ss_tags t inner join ss_tags g on g.titleKey=t.titleKey inner join searches s on s.idtags=t.idtags where ($where) and query='$term2Sql;mode=any;groupby=attr:titleKey;select=idtags as idtags,sum(TotalCount) as Count;sort=extended:@weight desc;groupsort=@weight desc, Count desc;limit=30;maxmatches=10000;maxquerytime=250' group by g.artistKey, g.titleKey) similar where simil<4 order by finalWeight desc;";
		      $maxSimil = min(ceil(mb_strlen($term2Key)*0.4), 3);
                      $urlTemplate = URL_SONG;
                      break;
    }
    //echo $sql; 
    if ($sql!=null) {
      $result = '';
      $similars = getSimilarTags($sql, $searching, $term1, $term1Key, $term2, $term2Key, $maxSimil, 3);
      $total = count($similars);
      for ($i = 0; $i<$total; $i++)
      {
	$pair = $similars[$i];
        if ($pair[0]!=null && $pair[0]!='') { 
		$urlArray[0] = wfUrlEncode($pair[0]); 
		$linkText = htmlspecialchars($pair[0]);
	} else {
		$urlArray[0] = wfUrlEncode($term1);
		$linkText = htmlspecialchars($term1);
	}

        if ($pair[1]!=null && $pair[1]!='') {
		$urlArray[1] = wfUrlEncode($pair[1]);
		$linkText .= " - ".htmlspecialchars($pair[1]);
	} else {
		$urlArray[1] = wfUrlEncode($term2);
		if ($term2!=null) $linkText .= " - ".htmlspecialchars($term2);
	}
        $url = createURL($urlTemplate, $urlArray);
	
	if ($result!='') {
		if ($i==$total-1) $result .= ' '.wfMsg('or').' ';
		else $result .= ', ';
	}
        $result .= "<a href='$url'>$linkText</a>";
      }
      if ($result!='') {
        $result = "<div id='didyoumean'>".wfMsg("Did you mean")." $result?</div>";
      }  
    }

    $out->mDidYouMean = $result;
}

function getSimilarTags($sql, $searching, $term1, $term1Key, $term2, $term2Key, $maxSimil, $maxCount)
{
  $ret = Array();
  $dbr = wfGetDB( DB_SLAVE );
  $res = $dbr->query( $sql );
  
  if ($row = $dbr->fetchobject($res)) {
     if ($searching) {
	$term1Norm = mb_trim(normalize($term1, true));
        $words = mb_split(" ", $term1Norm);
	$count = $maxCount;
        do {
           $phrase = "";
           foreach ($words as $word) {
              $distance = -1; $choosen = "";
              $tagWords = mb_split(" ", $row->artist);
              foreach ($tagWords as $tagWord) {
                 $nword = normalize($tagWord, false); if ($nword=='') continue;
		 $temp = log($row->artistWeight)/(mb_levenshtein($word, $nword)+1);
		 if ($temp>$distance) {
                    $choosen = $tagWord; 
                    $distance = $temp;
                 }
              }
              $tagWords = mb_split(" ", $row->album);
              foreach ($tagWords as $tagWord) {
		 $nword = normalize($tagWord); if ($nword=='') continue;
                 $temp = log($row->albumWeight)/(mb_levenshtein($word, $nword)+1);
		 if ($temp>$distance) { 
                    $choosen = $tagWord; 
                    $distance = $temp;
                 }
              }
              $tagWords = mb_split(" ", $row->title);
              foreach ($tagWords as $tagWord) {
		 $nword = normalize($tagWord); if ($nword=='') continue;
                 $temp = log($row->titleWeight)/(mb_levenshtein($word, $nword)+1);
		 if ($temp>$distance) { 
                    $choosen = $tagWord; 
                    $distance = $temp;
                 }
              }
              if ($distance==-1) continue;
              $phrase .= $choosen.' ';
           }
	   $phraseNorm = mb_trim(normalize($phrase, true));
           if ($distance!=-1) $editDistance = mb_levenshtein($term1Norm, $phraseNorm);
	   if ($editDistance==0) 
              $count--;
	   else if ($editDistance<=$maxSimil && $already[$phraseNorm]==null) { 
              $ret[$editDistance*1000000+(10000/$distance)*100+(crc32($phraseNorm)%100)] = array(mb_trim($phrase), null); 
              $already[$phraseNorm]=true;
           }
        } while ($count>0 && $row = $dbr->fetchobject($res));
	ksort($ret);
        array_splice($ret, $maxCount);
     } else {
	$exactTagWeight = $lastTagWeight = 0;
        $exactWeight = $lastWeight = $count = 0;
        $exactCount = $lastCount = 0;

        do {
           if ($row->term1Key==$term1Key && $row->term2Key==$term2Key) {
              $exactTagWeight = $row->tagWeight;
              $exactWeight = $row->finalWeight;
              $exactCount = $row->queryCount;
              break; //continue;
           } else if 
		//if (($exactWeight>1*$row->finalWeight && $exactCount>$row->queryCount && $exactTagWeight>1*$row->tagWeight) || 
                  ($lastWeight>2*$row->finalWeight && ($lastCount>$row->queryCount && $lastTagWeight>2*$row->tagWeight))
              break;
           else if ($row->simil > $maxSimil)
              continue;

           $ret[] = array($row->term1, $row->term2);
           $count++;
           $lastTagWeight = $row->tagWeight;
           $lastWeight = $row->finalWeight;
           $lastCount = $row->queryCount;
           if ($count>=$maxCount) break;
        } while ($row = $dbr->fetchobject($res));
     }
  }
  $dbr->freeResult( $res );

  return $ret;
}
  

